package com.qinghong.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.qinghong.blog.config.security.JwtTokenUtil;
import com.qinghong.blog.constant.CommonConst;
import com.qinghong.blog.dto.EmailDTO;
import com.qinghong.blog.dto.PageDTO;
import com.qinghong.blog.dto.UserBackDTO;
import com.qinghong.blog.entity.Role;
import com.qinghong.blog.entity.UserAuth;
import com.qinghong.blog.entity.UserInfo;
import com.qinghong.blog.entity.UserRole;
import com.qinghong.blog.enums.LoginTypeEnum;
import com.qinghong.blog.enums.RoleEnum;
import com.qinghong.blog.exception.BaseException;
import com.qinghong.blog.mapper.RoleMapper;
import com.qinghong.blog.mapper.UserAuthMapper;
import com.qinghong.blog.mapper.UserInfoMapper;
import com.qinghong.blog.mapper.UserRoleMapper;
import com.qinghong.blog.service.IUserAuthService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qinghong.blog.vo.ConditionVO;
import com.qinghong.blog.vo.PasswordVO;
import com.qinghong.blog.vo.Result;
import com.qinghong.blog.vo.UserVO;
import io.swagger.models.auth.In;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.qinghong.blog.constant.MQPrefixConst.EMAIL_EXCHANGE;
import static com.qinghong.blog.constant.MQPrefixConst.EMAIL_QUEUE;
import static com.qinghong.blog.constant.RedisPrefixConst.CODE_EXPIRE_TIME;
import static com.qinghong.blog.constant.RedisPrefixConst.CODE_KEY;
import static com.qinghong.blog.utils.CommonUtil.checkEmail;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author qinghong
 * @since 2021-12-06
 */
@Service
public class UserAuthServiceImpl extends ServiceImpl<UserAuthMapper, UserAuth> implements IUserAuthService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    public UserAuth getUserAuthByUserName(String username) {
        return baseMapper.selectOne(new QueryWrapper<UserAuth>()
                .eq("username",username));
    }

    @Override
    public List<Role> getRoles(Integer userId) {
        return roleMapper.getRolesByUserId(userId);
    }

    @Override
    public Result login(String username, String password, HttpServletRequest request) {
        // 登录
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (userDetails == null || !passwordEncoder.matches(password,userDetails.getPassword())){
            return Result.error("用户名或密码不正确");
        }
        if (!userDetails.isEnabled()){
            return Result.error("账户被禁用");
        }
        // 更新security登录用户对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 生成token
        String token = jwtTokenUtil.generateToken(userDetails);
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token",token);
        tokenMap.put("tokenHead",tokenHead);
        return Result.success("登录成功",tokenMap);
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 邮箱号
     */
    @Value("${spring.mail.username}")
    private String email;

    /**
     * qq appId
     */
    @Value("${qq.app-id}")
    private String QQ_APP_ID;

    /**
     * qq获取用户信息接口地址
     */
    @Value("${qq.user-info-url}")
    private String QQ_USER_INFO_URL;

    /**
     * 微博appId
     */
    @Value("${weibo.app-id}")
    private String WEIBO_APP_ID;

    /**
     * 微博appSecret
     */
    @Value("${weibo.app-secret}")
    private String WEIBO_APP_SECRET;

    /**
     * 微博授权方式
     */
    @Value("${weibo.grant-type}")
    private String WEIBO_GRANT_TYPE;

    /**
     * 微博回调地址
     */
    @Value("${weibo.redirect-url}")
    private String WEIBO_REDIRECT_URI;

    /**
     * 微博获取token和openId接口地址
     */
    @Value("${weibo.access-token-url}")
    private String WEIBO_ACCESS_TOKEN_URI;

    /**
     * 微博获取用户信息接口地址
     */
    @Value("${weibo.user-info-url}")
    private String WEIBO_USER_INFO_URI;

    @Override
    public void sendCode(String username) {
        // 校验账号是否合法
        if (!checkEmail(username)) {
            throw new BaseException(504,"请输入正确邮箱");
        }
        // 生成六位随机验证码发送
        StringBuilder code = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            code.append(random.nextInt(10));
        }
        // 发送验证码
        EmailDTO emailDTO = EmailDTO.builder()
                .email(username)
                .subject("验证码")
                .content("您的验证码为 " + code.toString() + " 有效期15分钟，请不要告诉他人哦！")
                .build();
        rabbitTemplate.convertAndSend(EMAIL_EXCHANGE, "*", new Message(JSON.toJSONBytes(emailDTO), new MessageProperties()));
        // 将验证码存入redis，设置过期时间为15分钟
        redisTemplate.boundValueOps(CODE_KEY + username).set(code);
        redisTemplate.expire(CODE_KEY + username, CODE_EXPIRE_TIME, TimeUnit.MILLISECONDS);
    }

    @Override
    public PageDTO<UserBackDTO> listUserBackDTO(ConditionVO condition) {
        // 转换页码
        condition.setCurrent((condition.getCurrent() - 1)*condition.getSize());
        // 获取后台用户数量
        Integer count = baseMapper.countUser(condition);
        if (count == 0){
            return new PageDTO<>();
        }
        // 获取后台用户列表
        List<UserBackDTO> userBackDTOList = baseMapper.listUser(condition);
        return new PageDTO<>(userBackDTOList, count);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveUser(UserVO user) {
        // 校验账号是否合法
        if (checkUser(user)) {
            throw new BaseException(506,"邮箱已被注册！");
        }
        // 新增用户信息
        UserInfo userInfo = UserInfo.builder()
                .email(user.getUsername())
                .nickname(CommonConst.DEFAULT_NICKNAME)
                .avatar(CommonConst.DEFAULT_AVATAR)
                .createTime(new Date())
                .build();
        userInfoMapper.insert(userInfo);
        // 绑定用户角色
        saveUserRole(userInfo);
        // 新增用户账号
        UserAuth userAuth = UserAuth.builder()
                .userInfoId(userInfo.getId())
                .username(user.getUsername())
                .password(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
                .createTime(new Date())
                .loginType(LoginTypeEnum.EMAIL.getType())
                .build();
        baseMapper.insert(userAuth);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePassword(UserVO user) {
        // 校验账号是否合法
        if (!checkUser(user)) {
            throw new BaseException(508,"邮箱尚未注册！");
        }
        // 根据用户名修改密码
        baseMapper.update(new UserAuth(), new LambdaUpdateWrapper<UserAuth>()
                .set(UserAuth::getPassword, BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
                .eq(UserAuth::getUsername, user.getUsername()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateAdminPassword(PasswordVO passwordVO) {
        Integer userId = ((UserAuth) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        // 查询旧密码是否正确
        UserAuth userAuth = baseMapper.selectOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getId, userId));
        // 正确则修改密码，错误则提示不正确
        if (Objects.nonNull(userAuth) && BCrypt.checkpw(passwordVO.getOldPassword(),userAuth.getPassword())){
            UserAuth user = UserAuth.builder()
                    .id(userId)
                    .password(BCrypt.hashpw(passwordVO.getNewPassword(), BCrypt.gensalt()))
                    .build();
            baseMapper.updateById(user);
        }else {
            throw new BaseException(504,"旧密码不正确!");
        }
    }


    /**
     * 绑定用户角色
     *
     * @param userInfo 用户信息
     */
    private void saveUserRole(UserInfo userInfo) {
        UserRole userRole = UserRole.builder()
                .userId(userInfo.getId())
                .roleId(RoleEnum.USER.getRoleId())
                .build();
        userRoleMapper.insert(userRole);
    }

    /**
     * 校验用户数据是否合法
     *
     * @param user 用户数据
     * @return 合法状态
     */
    private Boolean checkUser(UserVO user) {
        if (!user.getCode().equals(redisTemplate.boundValueOps(CODE_KEY + user.getUsername()).get())) {
            throw new BaseException(504,"验证码错误！");
        }
        //查询用户名是否存在
        UserAuth userAuth = baseMapper.selectOne(new LambdaQueryWrapper<UserAuth>()
                .select(UserAuth::getUsername).eq(UserAuth::getUsername, user.getUsername()));
        return Objects.nonNull(userAuth);
    }
}
